home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Developer Helper 1: Phil & Dave's Excellent CD
/
Excellent CD HFS.raw
/
Moof
/
Goodies
/
DTS Goodies
/
Hier⁄Popup Menu Demos
/
PopMenus.p
< prev
next >
Wrap
Text File
|
1989-04-13
|
12KB
|
332 lines
{*************************************************************************
Pop-up Menu Example
This program is a simple example of how to use pop-up menus in your
application. It implements a pop-up as a userItem in a modal dialog
box (this is a helpful example in its own right!). A few tips:
The Menu Manager provides just one routine, PopUpMenuSelect, that you
call when the user clicks on the current selection of your pop-up. Your
application is responsible for drawing the current selection (and the
title that goes with it), and hilighting the title before calling
PopUpMenuSelect, and unhilighting afterwards.
The metrics involved with drawing the current selection are a little
tricky; this example does things in the recommended way. Note the
following as you try the example (it helps to have a long font name
to see some of the extreme cases!):
- There is one pixel between the title's boundsrect (as defined in the
DITL, a staticText item) and the popup's (a userItem). Also, the
userItem's boundsrect (as defined) is used as the area INSIDE the
current selection box. This is the easiest way to handle this; it's
inconsistent in that normally, clicking on the drop shadow would get
you what you want (in this case, these clicks are ignored; the user
must click within the box). The only case where this is a problem
is if you filter the drawing of items in response to an update event
to those items that lie within the update region (if just the drop
shadow needed updating, it wouldn't be updated with this
implementation).
- The current selection is always drawn in the same place & same
size. To prevent the selection from drawing wider than the box,
the selection is truncated and ellipses added (if necessary). This
algorithm may be handy elsewhere, too! This particular example has
an especially narrow current selection box (to show off the truncation
feature); you'll probably want to make yours wide enough to accomodate
most possible values.
- The title is highlighted while the pop-up is up (that is, during
PopUpMenuSelect), and presumably, during the duration of any operation
generated by the chosen item. This is intended to work just like the
hilighting of a regular menu title.
[You need to have more than about seven font families in your system
file to see this one:] A "feature" of the Menu Manager is that it must
call the menu defproc just once to allocate the space beneath the menu.
Also, the menu must be kept onscreen (scrolling if necessary). Also,
the human interface dictates that the currently-chosen item should
appear under the mouse when the pop-up is presented. These three
requirements conflict; note the case where the last item is the current
selection.
The menu manager will be happy to put the current item whereever you
say it should, but may leave white space in the menu if it's necessary
to "pre-scroll" the menu to give the selection you asked for. This
white space appears because the bits beneath the menu are saved and
restored just once (each).
The upshot of all this is that this is a Menu Manager gotcha; there is
no workaround, but the problem may be fixed in future Systems.
Pop-up menus must be InsertMenu'd just like hierarchical submenus; they
should left in the menu list only while PopUpMenuSelect is being called.
The example handles this correctly.
Applications' pop-up menus' IDs, should always be in the range 1 through
235 (inclusive); desk accessories' pop-ups should use IDs 236 through
255. This really applies to all menus; it's most important for pop-up and
submenus, however.
This particular example is meant to show the mechanics of creating and
handling events from submenus; in the interest of simplicity, certain nice
features (like checkmarking the current font) have been omitted.
Have fun.
*************************************************************************}
PROGRAM PopMenus;
{*
* Pop-up Menu Example
* Bryan Stearns 05May87
*}
USES MemTypes, Quickdraw, OSIntf, ToolIntf, PackIntf;
{$R-} {no range checking}
{$D+} {Generate debug symbols}
CONST
myDLOGid = 128; {our dialog template's resource ID}
popMenuID = 128; {our menu's ID}
myALRTid = 129; {our “need right machine & sys software” alert}
{Items in our dialog box: there’s an OK button, and…}
iPopUp = OK+1; {the Pop-up userItem}
iPopPrompt = iPopUp+1; {the Prompt staticText}
iDefOKRing = iPopPrompt+1; {the OK-button-default-ring userItem}
{ASCII code for Return and Enter}
crCode = 13; {(these are keyboard-independent)}
enterCode = 3;
{constants for positioning the default item within its box}
leftSlop = 13; {leave this much space on left of title}
rightSlop = 5; { this much on right}
botSlop = 5; { this much below baseline}
VAR myDialog: DialogPtr; {our dialog pointer}
popUpBox: Rect; {boundsrect of our popUp's title box}
promptBox: Rect; {boundsrect of its prompt}
popMenu: MenuHandle; {our popUp's menu}
hitItem: INTEGER; {result of ModalDialog}
lastChoice: INTEGER; {the last-chosen item from the pop-up menu}
theType: INTEGER; {used as temp in GetDItem/SetDItem}
theHdl: Handle; {used as temp in GetDItem/SetDItem}
theBox: Rect; {used as temp in GetDItem/SetDItem}
{*
* Draw our popUp’s current selection box
* Note: This is called by the Dialog Manager (for
* update events) as well as our own Filterproc.
*}
PROCEDURE DrawPopUp(theDialog: DialogPtr; theItem: INTEGER);
VAR r: Rect;
curFont: Str255;
newWid, newLen, wid: INTEGER;
BEGIN
GetItem(popMenu,lastChoice,curFont); {get currently-selected item}
r := popUpbox;
WITH r DO BEGIN
InsetRect(r,-1,-1); {make it a little bigger}
{Make sure the title fits. Truncate it and add an ellipses (“…”)}
{if it doesn’t (by the way, “…” is option-semicolon)}
wid := (right - left) - (leftSlop + rightSlop); {available string area}
newWid := StringWidth(curFont); {get current width}
IF newWid > wid THEN BEGIN {doesn't fit - truncate it}
newLen := LENGTH(curFont); {current length in characters}
wid := wid - CharWidth('…'); {subtract width of ellipses}
REPEAT {until it fits (or we run out of characters)}
{drop the last character and its width}
newWid := newWid - CharWidth(curFont[newLen]);
newLen := PRED(newLen);
UNTIL (newWid <= wid) OR (LENGTH(curFont) = 0);
{add the ellipses character}
newLen := SUCC(newLen); {one more char}
curFont[newLen] := '…'; {it’s the ellipses}
curFont[0] := CHR(newLen); {fix the length}
END;
{draw the box and its drop shadow}
FrameRect(r);
MoveTo(right,top+2); LineTo(right,bottom);
LineTo(left+2,bottom);
{draw the string}
MoveTo(left+LeftSlop,bottom-BotSlop);
DrawString(curFont);
END;
END; {DrawPopUp}
{*
* Draw the ring around the OK button, the way that
* alert boxes get it. This procedure is only called
* by the Dialog Manager for update events.
*}
PROCEDURE DrawOKDefault(theDialog: DialogPtr; theItem: INTEGER);
VAR savePen: PenState;
BEGIN
GetPenState(savePen); {save the old pen state}
GetDItem(theDialog, theItem, theType, theHdl, theBox); {get the item’s rect}
PenSize(3,3); {make the pen fatter}
FrameRoundRect(theBox,16,16); {draw the ring}
SetPenState(savePen); {restore the pen state}
END; {DrawOKDefault}
{*
* Filterproc for our dialog box
* - supports Enter & Return --> OK.
* - watches for userItem hits
*}
FUNCTION myFilter(theDialog: DialogPtr; VAR theEvent: EventRecord; VAR itemHit: INTEGER): BOOLEAN;
VAR mouseLoc, popLoc: Point;
newChoice: INTEGER;
chosen,ignoreLong: LongInt;
BEGIN
itemHit := 0; {We return these two values. Initialize them.}
myFilter := FALSE;
SetPort(theDialog);
WITH theEvent DO CASE what OF
keyDown: BEGIN
IF (theEvent.message MOD 256) IN [crCode, enterCode] THEN BEGIN
{user pressed Return or Enter}
GetDItem(theDialog, OK, theType, theHdl, theBox); {get the button's rect}
HiliteControl(ControlHandle(theHdl), 1); {make it look...}
Delay(3, ignoreLong); {...like the OK button was hit}
myFilter := TRUE; {dialog is over}
itemHit := OK; {have ModalDialog return that the user hit OK}
END;
END; {keydown case}
mouseDown: BEGIN {"Click!"}
mouseLoc := where; {copy the mouse position}
GlobalToLocal(mouseLoc); {convert it to local coordinates}
{Was the click in our item?}
IF (FindDItem(theDialog, mouseLoc) + 1) = iPopUp THEN BEGIN {Yep, the click was ours!}
{We're going to pop up our menu. Insert our menu into the menu list,}
{then call CalcMenuSize (to work around a bug in the Menu Manager), }
{then call PopUpMenuSelect and let the user drag around. Note that the}
{(top,left) parameters to PopUpMenuSelect are our item’s, converted to}
{global coordinates.}
InvertRect(promptBox); {hilight the prompt}
InsertMenu(popMenu,-1); {insert our menu in the menu list}
popLoc := popUpBox.topLeft; {copy our item’s topleft}
LocalToGlobal(popLoc); {convert back to global coords}
CalcMenuSize(popMenu); {Work around Menu Mgr bug}
WITH popLoc DO chosen := PopUpMenuSelect(popMenu, v, h, lastChoice);
InvertRect(promptBox); {unhilight the prompt}
DeleteMenu(popMenuID); {remove our menu from the menu list}
{Was something chosen?}
IF chosen <> 0 THEN BEGIN {yep, something was chosen}
newChoice := LoWord(chosen); {get the chosen item number}
IF newChoice <> lastChoice THEN BEGIN
{the user chose an item other than the current one}
SetItemMark(popMenu,lastChoice,' '); {unmark the old choice}
SetItemMark(popMenu,newChoice,CHR(checkMark)); {mark the new choice}
lastChoice := newChoice; {update the current choice}
{Draw the new title}
EraseRect(popUpBox);
DrawPopUp(theDialog,iPopUp);
myFilter := TRUE; {dialog is over}
itemHit := iPopUp; {have ModalDialog return that the user changed items}
END; {if this choice was not the current choice}
END; {if something was chosen}
END; {if clicked in our userItem}
END; {mousedown case}
END {case}
END; {myFilter}
{*
* Main
*}
BEGIN
{Initialize all the usual managers}
InitGraf(@thePort);
InitFonts;
FlushEvents(everyEvent,0);
InitWindows;
InitMenus;
TEInit;
InitDialogs(NIL);
InitCursor;
{Check to make sure we’re running on a machine}
{capable of supporting pop-up menus}
(** SysEnvirons(xxx); **)
IF FALSE THEN BEGIN {Sorry, see your dealer}
hitItem := StopAlert(myALRTid,NIL); {put up the alert}
ExitToShell; {back to the finder}
END;
{Get a menu containing the current set of fonts}
popMenu := NewMenu(popMenuID,'notUsed'); {Create a menu (its title is ignored)}
AddResMenu(popMenu,'FONT'); {fill it with fonts}
lastChoice := 1; {make the first item the default}
SetItemMark(popMenu,1,CHR(checkMark)); {check it}
{Get our dialog box, and set up our useritem (the dialog template}
{defines this dialog to be hidden, so that it won’t be drawn until}
{we’ve installed our userItem’s drawing procedure)}
myDialog := GetNewDialog(myDLOGid,NIL,POINTER(-1));
{Find out where our popUp's rectangle is, and set its item handle to be}
{a pointer to our popup-drawing procedure}
GetDItem(myDialog,iPopUp,theType,theHdl,popUpbox); {get the rect}
SetDItem(myDialog,iPopUp,theType,@DrawPopUp,popUpbox); {set the procPtr}
{Find out where the prompt for our popUp is, so that we can invert its}
{rect when popping up our menu}
GetDItem(myDialog,iPopPrompt,theType,theHdl,promptBox); {get the rect}
{Move our default-OK-button userItem to around the OK button, and set its}
{item handle to be a pointer to our other drawing procedure}
GetDItem(myDialog,OK,theType,theHdl,theBox); {get the OK button's rect}
InsetRect(theBox,-4,-4); {make the rect a little bigger}
{set the same old type, our procptr, and the new box}
SetDItem(myDialog,iDefOKRing,userItem+itemDisable,@DrawOKDefault,theBox);
ShowWindow(myDialog); {show it, finally!}
REPEAT
ModalDialog(@myFilter,hitItem);
CASE hitItem OF {which item was hit, if any?}
iPopUp: BEGIN {he chose a new item in our pop-up}
{Do whatever needs to be done when the choice changes; }
{the on-screen current value box has already been updated.}
END; {iPopUp case}
{other items here}
END; {case}
UNTIL hitItem = OK;
HideWindow(myDialog); {hide the window, as feedback}
END.